/*
{{className}}.java
Copyright (C) 2010  Belledonne Communications, Grenoble, France

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

package {{packageName}};

{{#isAndroid}}
import androidx.annotation.Nullable;
import androidx.annotation.NonNull;
{{/isAndroid}}
{{#imports}}
import {{import}}
{{/imports}}
{{#isLinphoneFactory}}
{{#isAndroid}}
import android.content.Context;
import android.os.Build;
{{/isAndroid}}
{{#isLinphoneFactory}}
{{^isAndroid}}
import org.linphone.core.tools.java.LibraryLoader;
{{/isAndroid}}
{{/isLinphoneFactory}}

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

{{#isAndroid}}
import org.linphone.mediastream.Version;
{{/isAndroid}}
{{/isLinphoneFactory}}
import java.lang.StackTraceElement;
import org.linphone.mediastream.Log;

/**
{{#briefDoc}}
{{#lines}}
* {{{line}}}<br/>
{{/lines}}
{{/briefDoc}}
{{#detailedDoc}}
* <br/>
{{#lines}}
* {{{line}}}<br/>
{{/lines}}
{{/detailedDoc}}
*/
public {{#isLinphoneFactory}}abstract class{{/isLinphoneFactory}}{{#isNotLinphoneFactory}}interface{{/isNotLinphoneFactory}} {{className}} {
{{#enums}}
    public enum {{{className}}} {
    {{#values}}
        /**
        {{#briefDoc}}
        {{#lines}}
        * {{{line}}}<br/>
        {{/lines}}
        {{/briefDoc}}
        {{#detailedDoc}}
        * <br/>
        {{#lines}}
        * {{{line}}}<br/>
        {{/lines}}
        {{/detailedDoc}}
        */
        {{name}}({{{value}}}){{commarorsemicolon}}

    {{/values}}
        protected final int mValue;

        private {{{className}}} (int value) {
            mValue = value;
        }

        static public {{{className}}} fromInt(int value) throws RuntimeException {
            switch(value) {
            {{#values}}
            case {{{value}}}: return {{name}};
            {{/values}}
            default:
                throw new RuntimeException("Unhandled enum value " + value + " for {{{className}}}");
            }
        }

        public int toInt() {
            return mValue;
        }
    };

{{/enums}}
{{#isLinphoneFactory}}
    static Factory _Factory;

    public static final synchronized Factory instance() {
        try {
            if (_Factory == null) {
                _Factory = new FactoryImpl();
            }
        } catch (Exception e) {
            System.err.println("Cannot instanciate factory");
        }
        return _Factory;
    }

    /**
      * Gets the LoggingService singleton
      */
    abstract public LoggingService getLoggingService();

    /**
     * When enabled, sets the verbosity level to MESSAGE and every log will be printed into logcat.
     * @deprecated use {@link #enableVerboseLogs}, {@link #setLoggerDomain} and {@link LoggingService#setLogLevel} instead.
     */
    abstract public void setDebugMode(boolean enable, String tag);

    /**
     * Used to change the domain name in the app Logger (default is Linphone).
     */
    abstract public void setLoggerDomain(String domain);

    /**
     * When enabled logs are printed into logcat.
     * warning: This won't have any effect if there is a {@link LoggingServiceListener} set by the app!
     */
    abstract public void enableLogcatLogs(boolean enable);

    abstract public Core getCore(long ptr);

{{/isLinphoneFactory}}
{{#isLinphoneCore}}
    /**
      * Gets the mediastreamer's factory
      */
    public org.linphone.mediastream.Factory getMediastreamerFactory();
{{/isLinphoneCore}}
{{#methods}}
    /**
    {{#briefDoc}}
	{{#hidden}}
	* @hidden This has been artificially created for Kotlin to wrap this method as a property.<br/>
	{{/hidden}}
    {{#lines}}
    * {{{line}}}<br/>
    {{/lines}}
    {{/briefDoc}}
    {{#detailedDoc}}
    * <br/>
    {{#lines}}
    * {{{line}}}<br/>
    {{/lines}}
    {{/detailedDoc}}
    */
    {{#deprecated}}@Deprecated
    {{/deprecated}}{{#return_maybenil}}{{#isAndroid}}@Nullable{{/isAndroid}}
    {{/return_maybenil}}{{#return_notnil}}{{#isAndroid}}@NonNull{{/isAndroid}}
    {{/return_notnil}}{{#isLinphoneFactory}}abstract {{/isLinphoneFactory}}public {{return}} {{name}}({{params}}){{#exception}} throws CoreException{{/exception}};

{{/methods}}
    /**
      * Sets the object to store in this object user's data
      * @param data the object to store
      */
    {{#isLinphoneFactory}}abstract {{/isLinphoneFactory}}public void setUserData(Object data);

    /**
      * Gets the object stored in this object user's data
      * @return the object store if any, null otherwise
      */
    {{#isLinphoneFactory}}abstract {{/isLinphoneFactory}}public Object getUserData();

    /**
      * Gets the native pointer used by this class to make native method calls.
      * @return the nativer pointer, as long
      */
    {{#isLinphoneFactory}}abstract {{/isLinphoneFactory}}public long getNativePointer();

    {{#toStringNotFound}}{{#isNotLinphoneFactory}}public String toString();{{/isNotLinphoneFactory}}{{/toStringNotFound}}
}

class {{classImplName}} {{#isLinphoneFactory}}extends{{/isLinphoneFactory}}{{#isNotLinphoneFactory}}implements{{/isNotLinphoneFactory}} {{className}} {

    protected long nativePtr = 0;
    protected transient Object userData = null;{{#hasCoreAccessor}}
    protected Core core = null;{{/hasCoreAccessor}}
    protected boolean _isConst = false;

{{#isLinphoneFactory}}
    protected LoggingService loggingService = null;

    protected FactoryImpl() {
        nativePtr = init();
    }

    private native long init();
{{/isLinphoneFactory}}
{{#isNotLinphoneFactory}}
    protected {{classImplName}}(long ptr, boolean isConst) {
        nativePtr = ptr;{{#hasCoreAccessor}}
        core = getCore();{{/hasCoreAccessor}}
        _isConst = isConst;
    }
{{/isNotLinphoneFactory}}

    public long getNativePointer() {
        return nativePtr;
    }

{{#isLinphoneFactory}}
{{#isAndroid}}
    private static boolean loadOptionalLibrary(String s) {
        try {
            System.loadLibrary(s);
            return true;
        } catch (Throwable e) {
            android.util.Log.w("FactoryImpl", "Unable to load optional library " + s + ": " + e.getMessage());
        }
        return false;
    }
{{/isAndroid}}

    static {
{{^isAndroid}}
        try {
            LibraryLoader.loadOptionalLibrary("vpx");
            LibraryLoader.loadLibrary("bctoolbox");
            LibraryLoader.loadLibrary("ortp");
            LibraryLoader.loadLibrary("mediastreamer2");
            LibraryLoader.loadLibrary("liblinphone");
        } catch (IOException e) {
            throw new ExceptionInInitializerError(e);
        }
{{/isAndroid}}
{{#isAndroid}}
{{^isBundledAndroid}}
        System.loadLibrary("c++_shared");
        loadOptionalLibrary("ffmpeg-linphone");
        System.loadLibrary("bctoolbox");
        System.loadLibrary("ortp");
        System.loadLibrary("mediastreamer2");
{{/isBundledAndroid}}
        System.loadLibrary("linphone");
        Version.dumpCapabilities();
{{/isAndroid}}
    }

    private native Core getCore(long nativePtr, long ptr);
    @Override
    public Core getCore(long ptr) {
        return getCore(nativePtr, ptr);
    }

    @Override
    public LoggingService getLoggingService() {
        if (loggingService == null) {
            loggingService = new LoggingServiceImpl(0, false).get();
        }
        return loggingService;
    }

    @Override
    public native void setDebugMode(boolean enable, String tag);

    
    @Override
    public native void setLoggerDomain(String domain);

    
    @Override
    public native void enableLogcatLogs(boolean enable);

{{/isLinphoneFactory}}

    public boolean isConst() {
        return _isConst;
    }

{{#methods}}
    private native {{return_native}} {{name_native}}({{native_params}});
    @Override{{#return_maybenil}} {{#isAndroid}}@Nullable{{/isAndroid}}{{/return_maybenil}}{{#return_notnil}} {{#isAndroid}}@NonNull{{/isAndroid}}{{/return_notnil}}
    synchronized public {{return}} {{name}}({{params}}) {{#exception}}throws CoreException{{/exception}} {
        {{#hasCoreAccessor}}{{#isNotGetCore}}synchronized(core) { {{/isNotGetCore}}{{/hasCoreAccessor}}
        {{#cantBeCalledIfObjectIsConst}}if (_isConst) {
			try {
				throw new CoreException(this.toString() + " is const! If you want to call {{name}}() on it, clone it first.");
			} catch (CoreException e) {
				Log.e(e);
				for (StackTraceElement st : e.getStackTrace()) {
					Log.e(st);
				}
			}
		}{{/cantBeCalledIfObjectIsConst}}
        {{#exception}}int exceptionResult = {{/exception}}{{return_keyword}}{{#enumCast}}{{return}}.fromInt({{/enumCast}}{{#enumArrayCast}}{{enumName}}.fromIntArray({{/enumArrayCast}}{{#classCast}}({{return}}){{/classCast}}{{name_native}}({{native_params_impl}}){{#enumCast}}){{/enumCast}}{{#enumArrayCast}}){{/enumArrayCast}};{{#exception}}
        if (exceptionResult != 0) throw new CoreException("{{name}} returned value " + exceptionResult);{{/exception}}{{#hasCoreAccessor}}{{#isNotGetCore}}
        }{{/isNotGetCore}}{{/hasCoreAccessor}}
    }

{{/methods}}
{{#isLinphoneCore}}
    private native org.linphone.mediastream.Factory getMediastreamerFactory(long nativePtr);
    public org.linphone.mediastream.Factory getMediastreamerFactory() {
        return getMediastreamerFactory(nativePtr);
    }

{{/isLinphoneCore}}
{{#isNotLinphoneFactory}}
    private native boolean unref(long ptr, boolean isConst);
    protected void finalize() throws Throwable {
        /* Considering the following scenario:
        User scrolls fast in a chat message list that contains a lot of images, 
        so the VM will garbage collect often due to the images taking much space.
        The view itself only has a reference on the EventLogs, not the ChatMessages directly,
        so each call of onBindView will call eventLog.getChatMessage() and the reference will only be temporary.
        It has been observed a race condition in which the JNI level can obtain a LocalRef from a WeakGlobalRef
        of an object that has been scheduled for destruction and for which the finalize() method will be called shortly after.
        The result is a Java object that has been finalized can have it's methods called after the finalize() has terminated.*/
        
        /*The workaround we made here is to keep the nativePtr value even after the unref() only if the object is still reffed by the SDK,
        so the few methods calls following the finalize() will still work because we know the native pointer still exists.*/

        /*This solution will work as long as the objects still have a ref at C level, which is the case with ChatMessages because they are hold by the Events.
        Should we have this problem again in a situation where the C no longer holds a reference (resulting in the C object being destroyed by finalize()), 
        another solution is possible (but more complex to implement).
        This solution would consist in implementing at C level, a queue of belle_sip_object_t that are loosing their last ref. 
        Instead of being deleted immediately by belle_sip_object_unref() calling belle_sip_object_delete(), 
        unref() would place the object onto this "C finalizer queue", the delete() call would then be performed later, 
        by a hook added into the main android looper.
        This solution should work because the actual destruction will be performed from the looper context, 
        so in a place where the local java object causing our troubles no longer exists for sure.
        This solution has however the inconvenience that it can work only within an application that uses a single thread to call liblinphone API.*/
        
        if (nativePtr != 0) {
            boolean destroyed = unref(nativePtr, _isConst);
            if (destroyed) {
                nativePtr = 0;
            }
        }
        super.finalize();
    }
{{/isNotLinphoneFactory}}

    @Override
    public void setUserData(Object data) {
        userData = data;
    }

    @Override
    public Object getUserData() {
        return userData;
    }

    {{#toStringNotFound}}{{#isNotLinphoneFactory}}@Override
    public String toString() {
        return "Java object [" + super.toString() + "], native pointer [" + String.format("0x%08x", nativePtr) + "]";
    }{{/isNotLinphoneFactory}}{{/toStringNotFound}}
}
